JS原型继承之组合继承

如果有不了解原型的同学,可以查看这篇文章传送门

JS原型继承

在说组合继承之前,先说一下JS原型继承,这样更能体现出组合继承的强大!

function Person () {
this.name = '张三'
this.numbers = [1, 2]
}

Person.prototype.getName = function () {
console.log(this.name)
}

function Student () {
this.name = '李四'
}

Student.prototype = new Person()

var student = new Student()

student.getName() // 输出李四

但是原型继承有个缺点,请看下列代码:

var student1 = new Student()

var student2 = new Student()

student1.numbers.push(3)

console.log(student2.numbers) // 输出[1, 2, 3]

从下图我们可以看出,student1student2想要获取numbers这个属性值,就需要通过原型链,自身的__proto__属性,向上找,也就找到了Student.prototype,而Student.prototypePerson实例,它是有numbers属性值的,这样就获取到了nunbers,但是student1student2都能获取到这个属性,也都能修改,是共享的。这就是原型继承的缺点

1558429755197

为了解决这个缺点,出现了借用构造函数继承

借用构造函数继承

function Person (name) {
this.name = name
this.numbers = [1, 2]
}

Person.prototype.getName = function () {
console.log(this.name)
}

function Student (name, age) {
Person.call(this, name)
this.age = age
}

var student = new Student('张三', 18)

console.log(student) // Student { name: '张三', numbers: [ 1, 2 ], age: 18 }

借用构造函数继承,就是借用Person构造函数,改变Person构造函数的this的指向,将Person中的属性全部挂载到Studentthis中。这样的话由Student构造函数生成的实例就都有Person的属性值了,而且每一个实例都是独立互不干扰的。

var student1 = new Student()

var student2 = new Student()

student1.numbers.push(3)

console.log(student2.numbers) // 输出[1, 2]

缺点

子类不能调用父类的方法,也就是说Student的实例不能调用PersongetName方法。因为StudentPerson没有在原型链上面进行关联。为了解决这个缺点,就出现了组合继承。

组合继承

function Person (name) {
this.name = name
this.numbers = [1, 2]
}

Person.prototype.getName = function () {
console.log(this.name)
}

function Student (name, age) {
Person.call(this, name)
this.age = age
}

Student.prototype = new Person()

var student = new Student('张三', 18)

console.log(student)

组合继承也很简单,只需要将原型继承和借用构造函数继承组合起来就行了。这样原型继承和借用构造函数继承的缺点就解决了。

优化1

Student.prototype = Person.prototype

这样的话,Person这个构造函数就不用new两次了,节省性能。

优化2

如果只是这样优化的话,还不够,因为这样优化的话,StudentPerson就指向用一个原型对象,无法通过student.__proto__.constructor来判断实例到底是由哪个类生成的。

这时候有同学可能要问了,为什么不用instanceof来判断呢?

instanceof的原理就是判断实例对象的__proto__和构造函数的prototype是否是同一个引用。

student.__proto__ === Student.prototype === Person.prototype,所以用instanceof没用,返回结果都一样。

如果想要更严谨的判断一个实例对象是由哪个构造函数生成的,就要这样写。

object.__proto__.constructor === 构造函数

// Object.create创建的对象的原型对象是传入的参数,也就是Person.prototype
// 使用Object.create的目的是将Student.prototype和Person.prototype分开,
// 让两者不公用同一个原型对象
// 这样的话,Student的原型对象就是Object.create创建的对象,
// Person的原型对象就是Person.prototype
// 手动将Student的原型对象的constructor修改为Student,并不会影响到Person,
// 这样就可以判断实例是由哪个类生成的
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student

这样的话,就将问题解决了。